﻿using Autofac;
using Fasterflect;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Loong.Events.Bus.Handlers;
using Loong.Extensions;
using Loong.Threading;

namespace Loong.Events.Bus
{
    public class EventBus : IEventBus
    {
        private readonly ILifetimeScope _lifetimeScope;
        private readonly ConcurrentDictionary<Type, List<Type>> _handlerTypes;

        public EventBus(ILifetimeScope lifetimeScope)
        {
            _lifetimeScope = lifetimeScope;
            _handlerTypes = new ConcurrentDictionary<Type, List<Type>>();
        }

        public IDisposable Register<TEventData, TEventHandler>()
            where TEventData : IEventData
            where TEventHandler : IAsyncEventHandler<TEventData>
        {
            return Register(typeof(TEventData), typeof(TEventHandler));
        }

        public IDisposable Register(Type eventType, Type eventHandlerType)
        {
            GetOrCreateHandlerTypes(eventType).Locking(handlerTypes => handlerTypes.Add(eventHandlerType));

            return new HandlerUnregistrar(this, eventType, eventHandlerType);
        }

        public void Unregister(Type eventType, Type eventHandlerType)
        {
            GetOrCreateHandlerTypes(eventType).Locking(handlerTypes => handlerTypes.Remove(eventHandlerType));
        }

        private List<Type> GetOrCreateHandlerTypes(Type eventType)
        {
            return _handlerTypes.GetOrAdd(eventType, (type) => new List<Type>());
        }

        public Task TriggerAsync<TEventData>(TEventData eventData)
            where TEventData : IEventData
        {
            return TriggerAsync((object)null, eventData);
        }

        public Task TriggerAsync<TEventData>(object eventSource, TEventData eventData)
            where TEventData : IEventData
        {
            return TriggerAsync(typeof(TEventData), eventSource, eventData);
        }

        public Task TriggerAsync(Type eventType, IEventData eventData)
        {
            return TriggerAsync(eventType, null, eventData);
        }

        public async Task TriggerAsync(Type eventType, object eventSource, IEventData eventData)
        {
            var exceptions = new List<Exception>();

            eventData.EventSource = eventSource;

            await new SynchronizationContextRemover();

            foreach (var (specificEventType, handlerTypes) in GetHandlerTypes(eventType))
            {
                foreach (var handlerType in handlerTypes)
                {
                    if (IsAsyncEventHandler(handlerType))
                    {
                        await TriggerHandlingAsync(specificEventType, handlerType, eventData, exceptions);
                    }
                    else
                    {
                        var message = $"Event handler to register for event type {eventType.Name} does not implement IAsyncEventHandler<{eventType.Name}> interface!";
                        exceptions.Add(new LoongException(message));
                    }
                }
            }

            if (exceptions.Any())
            {
                if (exceptions.Count == 1)
                {
                    exceptions[0].ReThrow();
                }

                throw new AggregateException($"More than one error has occurred while triggering the event: {eventType}", exceptions);
            }
        }

        private IEnumerable<(Type, List<Type>)> GetHandlerTypes(Type eventType)
        {
            var handlerTypes = new List<(Type, List<Type>)>();

            foreach (var handlerType in _handlerTypes.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key)))
            {
                handlerTypes.Add((handlerType.Key, handlerType.Value));
            }

            return handlerTypes;
        }

        private static bool ShouldTriggerEventForHandler(Type eventType, Type handlerType)
        {
            if (handlerType == eventType)
            {
                return true;
            }

            if (handlerType.IsAssignableFrom(eventType))
            {
                return true;
            }

            return false;
        }

        private bool IsAsyncEventHandler(Type handlerType)
        {
            return handlerType.GetInterfaces()
                .Where(i => i.IsGenericType)
                .Any(i => i.GetGenericTypeDefinition() == typeof(IAsyncEventHandler<>));
        }

        private async Task TriggerHandlingAsync(Type eventType, Type handlerType, IEventData eventData, List<Exception> exceptions)
        {
            using (var scope = _lifetimeScope.BeginLifetimeScope())
            {
                var handler = scope.Resolve(handlerType);

                try
                {
                    var handleEventAsync = handlerType.DelegateForCallMethod("HandleEventAsync", eventType);
                    await (handleEventAsync(handler, eventData) as Task);
                }
                catch (TargetInvocationException ex)
                {
                    exceptions.Add(ex.InnerException);
                }
                catch (Exception ex)
                {
                    exceptions.Add(ex);
                }
            }
        }
    }
}
